package com.zh.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zh.constants.Constants;
import com.zh.mapper.OrderMapper;
import com.zh.mapstruct.OrderStructMapper;
import com.zh.pojo.bo.DriverSettingBo;
import com.zh.pojo.bo.OrderToDriverBo;
import com.zh.pojo.bo.PayOrderBo;
import com.zh.pojo.bo.PayResultBo;
import com.zh.pojo.domain.*;
import com.zh.pojo.dto.ConfirmAmountDto;
import com.zh.pojo.dto.DriverLocationDto;
import com.zh.pojo.dto.OrderDto;
import com.zh.pojo.result.GeoSearchResult;
import com.zh.pojo.result.OrderCalculateResult;
import com.zh.pojo.result.ProfitSharingResult;
import com.zh.pojo.vo.*;
import com.zh.remote.api.BigdataApi;
import com.zh.remote.api.CustomerApi;
import com.zh.remote.api.DriverApi;
import com.zh.remote.pojo.param.DriverPointResult;
import com.zh.remote.pojo.param.DriverSummaryResult;
import com.zh.result.JSONResult;
import com.zh.service.*;
import com.zh.utils.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.aspectj.weaver.ast.Var;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author 初四
 * @since 2024-06-29
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    private static final Date nowDate = new Date();
    @Autowired
    private CustomerApi customerApi;

    @Autowired
    private IChargeRuleStartService ruleStartService;

    @Autowired
    private IChargeRuleReturnService ruleReturnService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private IOrderBillService billService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private DefaultMQPullConsumer pullConsumer;

    @Autowired
    private IChargeRuleWaitService waitService;

    @Autowired
    private BigdataApi bigdataApi;

    @Autowired
    private DriverApi driverApi;
    @Autowired
    private IProfitsharingRuleBaseService ruleBaseService;

    @Autowired
    private IOrderProfitsharingService sharingService;
    /**
     * 注入redis分布式锁
     */
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void updateOrderStatus(PayResultBo payResultBo) {
        // 幂等处理 防止重复消费   保证同一笔订单只被消费一次  使用 分布式锁
        // TODO
        // 获取锁名
        String lockName = String.format(Constants.Lock.UPDATE_ORDERNO_KEY, payResultBo.getOrderNo());
        RLock lock = redissonClient.getLock(lockName);
        // 修改订单 状态
        try {
            // 上锁
            lock.lock();
            Order order = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, payResultBo.getOrderNo()));
            // 幂等处理 防止重复消费
            if(order == null || order.getStatus().equals(Constants.Order.ORDER_STATUS_NOT_PAY)) return;
            order.setUpdateTime(nowDate);
            // 修改订单状态 - 为已付款
            order.setStatus(Constants.Order.ORDER_STATUS_PAYED);
            super.updateById(order);
        } finally {
            if(lock.isLocked()){
                // 释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 乘客查询订单账单表
     * @param orderNo 订单号
     * @return
     */
    @Override
    public OrderDetailForCustomerVO selectCustomerOrderBill(String orderNo) {
        Order order = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(order, "未找到该订单");
        OrderBill orderBill = billService.getById(order.getId());
        AssertUtil.isNotNull(orderBill, "未找到该账单");
        OrderDetailForCustomerVO orderBillVO = OrderStructMapper.INSTANCE.orderBillAndOrderConvertOrderBillVO(order, orderBill);
        log.info("查询到账单：{}", JSON.toJSONString(orderBillVO));
        return orderBillVO;
    }


    /**
     * 司机发送账单
     *
     * @param orderNo
     */
    @Override
    public void sendPayOrder(String orderNo) {
        // 1、查询订单 - 修改订单状态
        Order order = selectByOrderNo(orderNo, Constants.Order.ORDER_STATUS_ENSURE);
        order.setStatus(Constants.Order.ORDER_STATUS_NOT_PAY);
        order.setUpdateTime(nowDate);

        // 查询分账
        OrderProfitsharing profitSharing = sharingService.getOne(new LambdaQueryWrapper<OrderProfitsharing>()
                .eq(OrderProfitsharing::getOrderNo, orderNo));

        PayOrderBo payOrderBo = new PayOrderBo();
        payOrderBo.setAmount(order.getRealOrderAmount());
        payOrderBo.setOrderNo(orderNo);
        payOrderBo.setPayUserId(order.getCustomerId());
        payOrderBo.setSubject("订单金额为:" + order.getRealOrderAmount());
        payOrderBo.setShardingAmount(profitSharing.getDriverIncomeAmount());
        payOrderBo.setToUserOpenId(profitSharing.getToUserOpenId());
        //使用MQ发送事务消息
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                Constants.MQ.TX_GROUP_PAY_ORDER,
                Constants.MQ.TOPIC_PAY_ORDER + ":" + Constants.MQ.TAGS_PAY_ORDER,
                MessageBuilder.withPayload(payOrderBo).build(),
                order);
        // 判断支付账单结果是否发送成功
        AssertUtil.isTrue(sendResult.getSendStatus() == SendStatus.SEND_OK, "账单发送失败");

    }


    /**
     * 查询订单信息
     *
     * @param orderNo
     * @param status
     */
    private Order selectByOrderNo(String orderNo, int status) {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo)
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .eq(Order::getStatus, status)
        );
        AssertUtil.isNotNull(order, "订单不存在");
        return order;
    }

    /**
     * 查询 司机账单信息
     *
     * @param orderNo
     * @return
     */
    @Override
    public OrderBillVO selectDriverOrderBill(String orderNo) {
        OrderBill orderBill = billService.getOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderNo, orderNo));
        AssertUtil.isNotNull(orderBill, "账单不存在");
        OrderProfitsharing sharing = sharingService.getOne(new LambdaQueryWrapper<OrderProfitsharing>()
                .eq(OrderProfitsharing::getOrderNo, orderNo));
        AssertUtil.isNotNull(sharing, "分账信息不存在");
        OrderBillVO orderBillVO = OrderStructMapper.INSTANCE.orderBillAndorderProfitsharingConvertOrderBillVO(orderBill, sharing);
        return orderBillVO;
    }


    /**
     * 司机确认费用
     * 2.1.参数判断，条件判断：状态必须是结束代驾
     * 2.2.设置过路费，停车费，其它费
     * 2.3.重新计算订单：真实金额
     * 2.4.司机分账计算
     *
     * @param confirmDto
     */
    @Override
    @Transactional
    public void confirm(ConfirmAmountDto confirmDto) {
        // 1、查询订单 - 修改订单状态， 修改订单实际金额
        Order order = selectByOrderNo(confirmDto.getOrderNo(), Constants.Order.ORDER_STATUS_COMPLETE_DRIVED);
        // 设置账单真实金额
        BigDecimal realAmount = order.getExpectsOrderAmount()
                .add(confirmDto.getOtherFree())
                .add(confirmDto.getTollFee())
                .add(confirmDto.getParkingFee());
        order.setEndTime(nowDate);
        order.setUpdateTime(nowDate);
        // 修改司机状态为 —— 确认费用
        order.setStatus(Constants.Order.ORDER_STATUS_ENSURE);
        // 订单真实金额
        order.setRealOrderAmount(realAmount);
        super.updateById(order);
        // 更新账单表
        updateBill(confirmDto, realAmount);

        // 2、远程调用司机 汇总数据
        JSONResult<DriverSummaryResult> driverSummaryResult = driverApi.getDriverSummary(StpUtil.getLoginIdAsLong());
        AssertUtil.isNotNull(driverSummaryResult.isSuccess(), "远程调用司机汇总数据失败");
        DriverSummaryResult summary = driverSummaryResult.getData();
        // 3、司机分账计算
        ProfitSharingResult sharingResult = ruleBaseService.DividendCalculation(realAmount, summary);
        // 4、保存账单表
        sharingService.createSharing(sharingResult, order);
    }


    /**
     * 更新账单表
     *
     * @param confirmDto
     * @param realAmount
     */
    private void updateBill(ConfirmAmountDto confirmDto, BigDecimal realAmount) {
        // 更新bill账单表
        OrderBill orderBill = billService.getOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderNo, confirmDto.getOrderNo()));
        orderBill.setParkingAmount(confirmDto.getParkingFee());
        orderBill.setTollAmount(confirmDto.getTollFee());
        orderBill.setOtherAmount(confirmDto.getOtherFree());
        //  订单总金额
        orderBill.setRealOrderAmount(realAmount);
        // 实付款金额 : 订单总金额 - 代金券金额 TODO
        orderBill.setRealPayAmount(realAmount.subtract(orderBill.getVoucherAmount()));
        orderBill.setUpdateTime(nowDate);
        billService.updateById(orderBill);
    }

    /**
     * 司机结束代驾
     *
     * @param orderNo
     */
    @Override
    @Transactional
    public void endDriving(String orderNo) {
        // 1、参数判断
        AssertUtil.isNotNull(orderNo, "订单号不能为空");
        Order order = selectByOrderNo(orderNo, Constants.Order.ORDER_STATUS_START_DRIVING);
        // 2、修改订单状态 开始代驾时间、 修改时间 ,
        order.setStatus(Constants.Order.ORDER_STATUS_COMPLETE_DRIVED);
        order.setEndTime(nowDate);
        order.setUpdateTime(nowDate);

        // 计算真实里程 —— 测量真实距离， 计算里程费用， 返程费用，
        // 远程调用Bigdata获取真实距离
        JSONResult<List<DriverPointResult>> listJSONResult = bigdataApi.selectDriverPointByOrderNo(order.getOrderNo());
        AssertUtil.isTrue(listJSONResult.isSuccess(), "远程调用失败");
        // 分段测距  —— 获得 总里程
        BigDecimal totalDistance = SegmentedRanging(listJSONResult);
        log.info("真实里程：{}", totalDistance);
        // 计算真实费用
        OrderCalculateResult calculateResult = calculateFee(totalDistance);

        // 实际里程
        order.setRealMileage(totalDistance);
        // 返回里程
        order.setReturnMileage(totalDistance);
        OrderBill orderBill = billService.getById(order.getId());
        AssertUtil.isNotNull(orderBill, "订单账单不存在");
        // 预估金额
        order.setExpectsOrderAmount(calculateResult.getExpectsOrderAmount().add(orderBill.getWaitingAmount()).setScale(2, BigDecimal.ROUND_HALF_UP));
        // 更新数据库- 修改订单
        super.updateById(order);
        // 更新Bill信息
        OrderStructMapper.INSTANCE.updateOrderBillFromOrderCalculateResult(calculateResult, orderBill);
        billService.updateById(orderBill);

    }


    /**
     * 分段测距
     *
     * @param listJSONResult
     */
    private BigDecimal SegmentedRanging(JSONResult<List<DriverPointResult>> listJSONResult) {
        List<DriverPointResult> pointList = listJSONResult.getData();
        // 总里程
        BigDecimal totalDistance = BigDecimal.ZERO;

        for (int i = 0; i < pointList.size() - 1; i++) {
            // 获取开始坐标位置
            DriverPointResult startPoint = pointList.get(i);
            DriverPointResult endPoint = pointList.get(i + 1);
            // 计算两点之间距离
            double distance = calculateDistance(
                    Double.parseDouble(startPoint.getLongitude()),
                    Double.parseDouble(startPoint.getLatitude()),
                    BigDecimal.ZERO.doubleValue(),
                    Double.parseDouble(endPoint.getLongitude()),
                    Double.parseDouble(endPoint.getLatitude()),
                    BigDecimal.ZERO.doubleValue());
            totalDistance = totalDistance.add(BigDecimal.valueOf(distance)).divide(BigDecimal.valueOf(1000));
        }
        return totalDistance;
    }

    public static void main(String[] args) {
        // 天府广场：104.065901, 30.657372, 0.0
        // 成都东站：104.141478, 30.628834, 0.0
        System.out.println("两点间的直线距离：" + calculateDistance(
                104.065928, 30.632305, 0.0,
                104.072141, 30.489682, 0.0
        ));
    }

    /**
     * 计算地球上两点之间的直线距离，同时考虑海拔高度差
     * 如果你不需要考虑海拔高度，el1 和 el2 可以都传入 0.0
     *
     * @param lon1 起点的经度
     * @param lat1 起点的纬度
     * @param el1  起点的海拔
     * @param lon2 终点的经度
     * @param lat2 终点的纬度
     * @param el2  终点的海拔
     * @return 两点之间的直线距离，单位米
     */
    private static double calculateDistance(double lon1, double lat1, double el1,
                                            double lon2, double lat2, double el2) {
        // 这个星球的半径（此处为地球）
        final int R = 6371;

        double latDistance = Math.toRadians(lat2 - lat1);
        double lonDistance = Math.toRadians(lon2 - lon1);
        double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        // 将距离转换为单位米
        double distance = R * c * 1000;

        double height = el1 - el2;
        distance = Math.pow(distance, 2) + Math.pow(height, 2);

        return Math.sqrt(distance);
    }

    /**
     * 司机开始代驾
     *
     * @param orderNo
     */
    @Override
    @Transactional
    public void startDriving(String orderNo) {
        // 1、参数判断
        AssertUtil.isNotNull(orderNo, "订单号不能为空");
        Order order = selectByOrderNo(orderNo, Constants.Order.ORDER_STATUS_ARRIVE);

        // 2、修改订单状态 开始代驾时间、 修改时间 ,
        order.setStatus(Constants.Order.ORDER_STATUS_START_DRIVING);
        order.setStartTime(nowDate);
        order.setUpdateTime(nowDate);
        super.updateById(order);

        // 3、计算等时费  司机开始代驾时间  - 司机到达时间
        List<ChargeRuleWait> waitList = waitService.list();
        if (waitList.size() == 0) {
            return;
        }
        ChargeRuleWait wait = waitList.get(0);
        // 等待时长
        BigDecimal waitingTime = BigDecimal.valueOf(DateUtil.between(order.getStartTime(), order.getArriveTime(), DateUnit.SECOND));
        BigDecimal waitAmount = BigDecimal.ZERO;
        OrderBill orderBill = billService.getById(order.getId());

        // 超过等待时长 ——  等待时长 > 免费等待分钟数
        if (waitingTime.compareTo(BigDecimal.valueOf(wait.getFreeBaseWaitingMinute())) > 1) {
            // 超出免费等时分钟数
            BigDecimal exceededWaitingTime = waitingTime.subtract(BigDecimal.valueOf(wait.getFreeBaseWaitingMinute()));
            orderBill.setExeceedBaseWaitingMinute(exceededWaitingTime);
            // 等时费
            waitAmount = wait.getExceedEveryMinuteAmount().multiply(exceededWaitingTime);

        }
        //  4、保存bill账单
        orderBill.setFreeBaseWaitingMinute(wait.getFreeBaseWaitingMinute());
        orderBill.setWaitingMinute(waitingTime.intValue());
        orderBill.setExeceedBaseWaitingEveryKmAmount(wait.getExceedEveryMinuteAmount());
        orderBill.setWaitingAmount(waitAmount);
        orderBill.setUpdateTime(nowDate);
        billService.updateById(orderBill);

    }

    /**
     * 司机到达代驾点
     *
     * @param orderNo
     */
    @Override
    @Transactional
    public void driverArrive(String orderNo) {
        // 1、参数判断
        AssertUtil.isNotNull(orderNo, "订单号不能为空");
        Order order = selectByOrderNo(orderNo, Constants.Order.ORDER_STATUS_ACCEPTED);
        order.setUpdateTime(nowDate);
        // 司机到达时间
        order.setArriveTime(nowDate);
        order.setStatus(Constants.Order.ORDER_STATUS_ARRIVE);
        super.updateById(order);
    }


    /**
     * 加载订单位置
     *
     * @param orderNo
     * @return
     */
    @Override
    public OrderLocationVo loadOrderLocatin(String orderNo) {
        AssertUtil.isNotNull(orderNo, "订单号不能为空");
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(order, "订单不存在");
        OrderLocationVo orderLocationVo = OrderStructMapper.INSTANCE.orderConvertKillOrderLocationVo(order);
        return orderLocationVo;
    }


    /**
     * 从redis中 拉取司机坐标位置， 订单号，订单状态
     *
     * @param orderNo
     * @return
     */
    @Override
    public DriverLocationDto pullDriverLocation(String orderNo) {
        String locationKey = String.format(Constants.Redis.DRIVER_LOCATION, orderNo);
        DriverLocationDto driverLocationDto = (DriverLocationDto) redisTemplate.opsForValue().get(locationKey);
        log.info("从redis中拉取司机坐标位置， 订单号，订单状态 : {}", driverLocationDto);
        AssertUtil.isNotNull(driverLocationDto, "司机坐标、订单号为空");
        return driverLocationDto;
    }

    /**
     * 缓存司机坐标、订单号、订单状态 到redis中
     *
     * @param locationDto
     */
    @Override
    public void cacheLocationPoint(DriverLocationDto locationDto) {
        redisTemplate.opsForValue().set(String.format(Constants.Redis.DRIVER_LOCATION, locationDto.getOrderNo()), locationDto);
    }

    /**
     * 加载乘客进行中的订单
     *
     * @return
     */
    @Override
    public Order selectCustomerInProcessOrder() {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS));
        AssertUtil.isNotNull(order, "当前订单不存在");
        return order;
    }


    /**
     * 加载司机进行中的订单
     *
     * @return
     */
    @Override
    public Object OrdersInProgressToDriver() {
        // 1、获取当前司机ID，根据订单状态是否是进行中的订单（如，1已接单，2司机已到达，3开始代驾，4结束代驾，5.司机确认费用）
        Order order = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS_FOR_DRIVER));
        // 如果订单不为空，则返回订单数据
        if (order != null) {
            //  返回订单数据
            KillOrderVo killOrderVo = OrderStructMapper.INSTANCE.orderConvertKillOrderVo(order);
            return killOrderVo;
        }
        return null;
    }


    /**
     * 乘客取消订单
     * 1、对订单号进行参数判断
     * 2、判断是否为当前乘客ID 取消订单， 判断订单状态是否是待接单.
     * 3、修改订单状态为 乘客取消订单
     *
     * @param orderNo
     */
    @Override
    @Transactional
    public void autoCancelOrder(String orderNo) {
        // 对订单号进行参数判断
        AssertUtil.isNotEmpty(orderNo, "订单号不能为空");
        //判断是否为当前乘客ID 取消订单， 判断订单状态是否是待接单
        Order order = this.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo)
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .eq(Order::getStatus, Constants.Order.ORDER_STATUS_WAIT));
        AssertUtil.isNotNull(order, "订单不存在");
        AssertUtil.isEquals(order.getStatus(), Constants.Order.ORDER_STATUS_WAIT, "订单状态异常");
        //修改订单状态为已取消， 更新数据库数据
        order.setStatus(Constants.Order.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
        order.setUpdateTime(new Date());
        this.updateById(order);
    }


    /**
     * 司机抢单-思路实现
     * 使用redis 分布式锁 将当前订单锁住，防止多个司机抢同一个订单
     * 根据订单号从redis中获取订单
     * 参数判断 : 判断是否是 下的订单，判断订单状态是否是待接单
     * 修改订单状态为已接单， 更新数据库数据
     * 删除redis中的订单
     *
     * @param orderNo
     * @return
     */
    @Override
    @Transactional
    public KillOrderVo killOrder(String orderNo) {
        AssertUtil.isNotEmpty(orderNo, "订单号不能为空");
        //将订单号作为锁的名字, 方便对每一个订单号加锁，减少加锁的颗粒度
        String lockName = String.format(Constants.Lock.KILL_ORDERNO_KEY, orderNo);
        RLock lock = redissonClient.getLock(lockName);
        try {
            // 上锁
            lock.lock();
            log.info("加锁成功 {}", lock);
            Order order = (Order) redisTemplate.opsForValue().get(String.format(Constants.Redis.ORDER_KEY, orderNo));
            AssertUtil.isNotNull(order, "订单不存在");
            // 判断订单状态是否是待接单
            AssertUtil.isEquals(order.getStatus(), Constants.Order.ORDER_STATUS_WAIT, "订单状态异常");
            // 修改订单状态为已接单， 更新数据库数据
            long driverId = StpUtil.getLoginIdAsLong();
            // 修改司机状态为 已接单
            order.setStatus(Constants.Order.ORDER_STATUS_ACCEPTED);
            LoginVo loginInfo = (LoginVo) redisTemplate.opsForValue().get(String.format(Constants.Redis.LOGIN_INFO_KEY, driverId));
            order.setDriverId(driverId);
            order.setDriverName(loginInfo.getNickName());
            order.setDriverPhone(loginInfo.getPhone());
            order.setUpdateTime(nowDate);
            order.setAcceptTime(nowDate);
            this.updateById(order);
            // 删除redis中的订单
            redisTemplate.delete(String.format(Constants.Redis.ORDER_KEY, orderNo));
            // 返回抢单后的数据；
            KillOrderVo killOrderVo = OrderStructMapper.INSTANCE.orderConvertKillOrderVo(order);
            return killOrderVo;
        } finally {
            if (lock.isLocked()) {
                //释放锁
                lock.unlock();
            }
            log.info("释放锁成功 {}", lock);
        }

    }


    /**
     * 司机拉取订单
     * <p>
     * 1、通过mq拉取新的订单
     *
     * @return
     */
    @Override
    public List<OrderToDriverBo> pullNewOrders() {
        //司机的ID
        Long loginId = Long.valueOf(StpUtil.getLoginId().toString());
        try {
            //订阅消息队列，指定topic
            Set<MessageQueue> messageQueues = pullConsumer.fetchSubscribeMessageQueues(Constants.MQ.TOPIC_ORDER);
            List<OrderToDriverBo> result = new ArrayList<>();
            for (MessageQueue messageQueue : messageQueues) {
                //long offset = pullConsumer.searchOffset(messageQueue, System.currentTimeMillis());
                //通过偏移量来获取消息
                long offset = pullConsumer.fetchConsumeOffset(messageQueue, true);
                //拉取消息，需要指定tags来区分司机
                PullResult pullResult = pullConsumer.pull(messageQueue, String.format(Constants.MQ.TAGS_ORDER_DRIVER_ID, loginId), offset, 32);
                //找到消息
                if (pullResult != null && pullResult.getPullStatus().equals(PullStatus.FOUND)) {
                    List<MessageExt> messageExtList = pullResult.getMsgFoundList();
                    if (messageExtList == null || messageExtList.size() == 0) continue;
                    for (MessageExt messageExt : messageExtList) {
                        //拿到消息内容，转换为订单对象
                        String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
                        OrderToDriverBo orderMesssage = JSON.parseObject(message, OrderToDriverBo.class);
                        //添加到结果列表
                        result.add(orderMesssage);
                        log.info("获取订单消息 {}", orderMesssage);
                    }
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    pullConsumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset());
                }
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 创建订单
     * 1、参数判断 , 判断是否有以进行的订单 , 判断乘客是否有未交罚款
     * 2.计算费用 （计算里程费， 返程费）
     * 3、保存订单 ； 构建order表， 构建bill表
     * 4、匹配司机 Redis —— EGO， 以订单开始为中心进行范围匹配
     * 5、把订单推送给司机： 使用RocketMQ异步推送
     *
     * @param orderDto 传递参数
     * @return
     */
    @Override
    @Transactional
    public String createOrder(OrderDto orderDto) {
        // 1、参数判断 , 判断是否有以进行的订单 , 判断乘客是否有未交罚款
        // 1.1 获取乘客id
        long customerId = StpUtil.getLoginIdAsLong();
        // 1.2 判断是否有未完成的订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getCustomerId, customerId)
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS));
        AssertUtil.isNull(order, "您有未完成的订单，请先完成订单");
        // 1.3 判断乘客是否有未交罚款
        // 远程调用 乘客罚款记录
        JSONResult<Boolean> customerHasFine = customerApi.customerHasFine(customerId);
        // 断言判断
        AssertUtil.isTrue(customerHasFine.isSuccess(), "远程调用失败，请联系管理员");
        AssertUtil.isFalse(customerHasFine.getData(), "您有未交罚款，请先缴纳罚款");

        // 2.计算费用 （计算里程费， 返程费）
        OrderCalculateResult calculateResult = calculateFee(orderDto.getExpectsMileage());
        // 3、保存订单 ； 构建order表， 构建bill表
        // 3、1 保存订单
        order = saveOrder(orderDto, calculateResult.getExpectsOrderAmount());
        // 3、2 构建bill表
        saveBill(order, calculateResult);
        // 4、匹配司机 Redis —— EGO， 以订单开始为中心进行范围匹配
        List<GeoSearchResult> searchResults = searchDriverGeo(Double.valueOf(orderDto.getStartPlaceLongitude()), Double.valueOf(orderDto.getStartPlaceLatitude()));
        AssertUtil.isNotEmpty(searchResults, "附近没有司机,请稍后重试");
        Boolean matchingDrivers = false;
        // 5、司机过滤
        for (GeoSearchResult result : searchResults) {
            //通过redis 查询司机配置
            String key = String.format(Constants.Redis.DRIVER_SETTING_KEY, result.getMember());
            DriverSettingBo settingBo = (DriverSettingBo) redisTemplate.opsForValue().get(key);
            AssertUtil.isNotNull(settingBo, "附近没有司机,请稍后重试");
            BigDecimal distance = new BigDecimal(settingBo.getOrderDistance());
            if (settingBo == null) {
                continue;
            }
            // 对司机的接单范围和 订单距离进行过滤
            if (Double.valueOf(settingBo.getRangeDistance()).compareTo(result.getDistance()) == -1
                    || distance.compareTo(order.getExpectsMileage()) == -1) {
                continue;
            }
            matchingDrivers = true;
            sendOrderToDriverFromMQ(order, result);
        }
        AssertUtil.isTrue(matchingDrivers, "附近没有司机，请换个地方试试~~");
        // 将订单存储到Redis中, 方便司机抢单时从Redis中搜索, 避免了对数据库的查询,防止高并发的场景
        redisTemplate.opsForValue().set(String.format(Constants.Redis.ORDER_KEY, order.getOrderNo()), order);
        return order.getOrderNo();
    }

    private void sendOrderToDriverFromMQ(Order order, GeoSearchResult result) {
        // 5、构建orderToDriverBo
        OrderToDriverBo orderToDriverBo = new OrderToDriverBo();
        orderToDriverBo.setDistance(BigDecimal.valueOf(result.getDistance()));
        orderToDriverBo.setMileage(order.getExpectsMileage());
        orderToDriverBo.setFavourAmount(order.getFavourAmount());
        orderToDriverBo.setExpectsFee(order.getExpectsOrderAmount());
        orderToDriverBo.setFrom(order.getStartPlace());
        orderToDriverBo.setTo(order.getEndPlace());
        orderToDriverBo.setOrderNo(order.getOrderNo());
        // 5、把订单推送给司机： 使用RocketMQ同步推送
        Message<?> message = MessageBuilder.withPayload(orderToDriverBo).build();
        String destination = Constants.MQ.TOPIC_ORDER + ":" + String.format(Constants.MQ.TAGS_ORDER_DRIVER_ID, result.getMember());
        SendResult sendResult = rocketMQTemplate.syncSend(destination, message);
        log.info("订单推送结果：{}", sendResult);
    }


    /**
     * 使用redis GEO 搜索司机坐标
     */
    private List<GeoSearchResult> searchDriverGeo(double Longitude, double Latitude) {
        //设置当前位置
        // // Point 中 x：经度"longitude":114.56，y：纬度"latitude":38.13
        Point point = new Point(Longitude, Latitude);
        //设置半径范围 (KILOMETERS 千米；METERS 米)
        Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
        Distance distance = new Distance(10, metric);
        Circle circle = new Circle(point, distance);
        //设置参数 包括距离、坐标、条数
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs()
                .includeDistance()      //包含距离
                .includeCoordinates()   //包含经纬度
                .sortAscending()        //正序排序
                .limit(10);      //条数

        //1、按照范围搜索司机坐标
        GeoResults<RedisGeoCommands.GeoLocation<Object>> geoResults = redisTemplate.opsForGeo()
                .radius(Constants.Redis.DRIVER_GEO_KEY, circle, geoRadiusCommandArgs);
        // 打印结果
        log.info("GEO搜索结果：{}", geoResults);

        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> contents = geoResults.getContent();
        // 返回结果对象
        List<GeoSearchResult> geoSearchResults = new ArrayList<>(contents.size());
        // 获取搜索结果
        for (GeoResult<RedisGeoCommands.GeoLocation<Object>> geoResult : contents) {
            // 获取 司机member （这里是获取redis中存储的司机Id）
            Object resultName = geoResult.getContent().getName();
            // 获取 司机到乘客的距离
            double resultDistance = geoResult.getDistance().getValue();
            geoSearchResults.add(new GeoSearchResult().setDistance(resultDistance).setMember(resultName));
        }
        return geoSearchResults;

    }

    /**
     * 构建Bill账单表
     *
     * @param order           订单信息
     * @param calculateResult 订单计算结果对象
     */
    private void saveBill(Order order, OrderCalculateResult calculateResult) {
        OrderBill bill = OrderStructMapper.INSTANCE.orderAndorderCalculateResultConvertOrderBill(order, calculateResult);
        bill.setCreateTime(nowDate);
        billService.save(bill);
    }

    /**
     * 创建（保存）订单
     *
     * @param orderDto           传递参数
     * @param expectsOrderAmount 预估总金额
     * @return
     */
    private Order saveOrder(OrderDto orderDto, BigDecimal expectsOrderAmount) {
        Order order = OrderStructMapper.INSTANCE.orderDtoConvertOrder(orderDto);
        // 构建唯一订单号（hutool工具的雪花生成唯一ID）
        order.setOrderNo(IdUtil.createSnowflake(1, 1).nextIdStr());
        // 乘客Id
        order.setCustomerId(StpUtil.getLoginIdAsLong());
        // 预估-返回里程
        order.setReturnMileage(orderDto.getExpectsMileage());
        // 预估订单价格
        order.setExpectsOrderAmount(expectsOrderAmount);
        // 好处费
        order.setFavourAmount(BigDecimal.ZERO);
        // 系统奖励费
        order.setIncentiveAmount(BigDecimal.ZERO);
        order.setCreateTime(nowDate);
        // 订单状态
        order.setStatus(Constants.Order.ORDER_STATUS_WAIT);
        order.setRemark("乘客" + StpUtil.getLoginIdAsLong() + "已下单,预估费用费为" + expectsOrderAmount);
        order.setIncentiveFeeStatus(false);
        // 通过在redis 中查询乘客用户信息
        String loginInfoKey = String.format(Constants.Redis.LOGIN_INFO_KEY, StpUtil.getLoginIdAsLong());
        LoginVo loginInfo = (LoginVo) redisTemplate.opsForValue().get(loginInfoKey);
        order.setCustomerName(loginInfo.getNickName());
        order.setCustomerPhone(loginInfo.getPhone());
        order.setCustomerPhoto(loginInfo.getAvatar());
        super.save(order);
        return order;
    }

    /**
     * 计算费用 （计算里程费， 返程费）
     *
     * @param expectsMileage 里程
     */
    private OrderCalculateResult calculateFee(BigDecimal expectsMileage) {
        // 获取下单时间
        int hour = DateUtil.hour(nowDate, true);
        // 获取起步价计价规则
        ChargeRuleStart ruleStart = ruleStartService.getOne(new LambdaQueryWrapper<ChargeRuleStart>()
                .le(ChargeRuleStart::getHourStart, hour)
                .gt(ChargeRuleStart::getHourEnd, hour));
        AssertUtil.isNotNull(ruleStart, "计价规则不存在");
        // 创建订单计算结果对象
        OrderCalculateResult calculateResult = new OrderCalculateResult();
        calculateResult.setBaseMileage(ruleStart.getBaseMileage());
        calculateResult.setBaseMileageAmount(ruleStart.getAmount());
        calculateResult.setExceedBaseMileageEveryKmAmount(ruleStart.getExceedEveryKmAmount());
        // 超出基础里程多少公里
        BigDecimal exceedMileage = BigDecimal.ZERO;
        // 超出基础里程的价格
        BigDecimal exceedAmount = BigDecimal.ZERO;
        //  获取起步价
        BigDecimal mileageAmount = ruleStart.getAmount();
        // 1、计算里程费
        if (expectsMileage.compareTo(ruleStart.getBaseMileage()) == 1) {
            // 超出里程 （预估里程 - 基础里程）
            exceedMileage = expectsMileage.subtract(ruleStart.getBaseMileage());
            //  超出里程费用 =  超出里程 * 超出里程每公里价格
            exceedAmount = exceedMileage.multiply(ruleStart.getExceedEveryKmAmount());
            // 预估里程费 =  基础里程费（起步价） + 超出里程费用
            mileageAmount = exceedAmount.add(ruleStart.getAmount());
        }
        calculateResult.setExceedBaseMileageAmount(exceedAmount);
        calculateResult.setExceedBaseMileage(exceedMileage);
        //打印日志预估里程费
        log.info("预估返程费为: {}", mileageAmount);
        calculateResult.setMileageAmount(mileageAmount);

        // 计算返程费
        ChargeRuleReturn ruleReturn = ruleReturnService.list().get(0);
        AssertUtil.isNotNull(ruleReturn, "计价规则不存在");

        BigDecimal returnAmount = BigDecimal.ZERO;
        // 超出免费返程部分的里程
        BigDecimal exceedReturnMileage = BigDecimal.ZERO;
        if (expectsMileage.compareTo(ruleReturn.getFreeBaseReturnMileage()) == 1) {
            // 超出返回免费里程
            exceedReturnMileage = expectsMileage.subtract(ruleReturn.getFreeBaseReturnMileage());
            // 返程费 =  超出免费返程部分的里程 * 每公里价格
            returnAmount = exceedReturnMileage.multiply(ruleReturn.getExceedEveryKmAmount());
        }
        calculateResult.setExceedFreeBaseReturnMileage(exceedReturnMileage);
        calculateResult.setReturnAmont(returnAmount);
        //打印日志返程费
        log.info("预估返程费为: {}", returnAmount);
        calculateResult.setReturnMileage(expectsMileage);
        calculateResult.setFreeBaseReturnMileage(ruleReturn.getFreeBaseReturnMileage());
        calculateResult.setExceedBaseReturnEveryKmAmount(ruleReturn.getExceedEveryKmAmount());
        // 计算预估金额 (里程费+ 返程费)
        BigDecimal totalAmount = mileageAmount.add(returnAmount).setScale(2, BigDecimal.ROUND_HALF_UP);
        calculateResult.setExpectsOrderAmount(totalAmount);
        return calculateResult;

    }


}
